<template>
  <div style="display: flex; height: 100%">
    <div style="flex: 0 0 240px">
      <div
        style="
          font-size: 32px;
          font-weight: bold;
          text-align: center;
          color: #1677ff;
        "
      >
        CCSC
        <span style="font-size: 16px"><CommentOutlined /> Assistant </span>
      </div>
    </div>
    <div style="flex: 1 1 auto">
      <div style="display: flex; flex-direction: column; height: 100%">
        <div style="flex: 0 0 24px; padding: 8px;">
          <a-tooltip>
            <template #title>Expand</template>
            <a-button style="height: 30px" size="small">
              <MenuFoldOutlined />
            </a-button>
          </a-tooltip>
        </div>

        <!-- 聊天记录 --->
        <div ref="chatContainer" style="flex: 1 1 auto; padding: 8px; overflow: auto" @scroll="handleScroll">
          <div
            :style="
              {display: 'flex', 
              padding: '16px',
              flexDirection: item.role=='assistant' ? 'row' : 'row-reverse'}
            "
            v-for="(item, idx) in chatListRevers"
            :key="item.id"
          >
            <div style="flex: 0 0 24px;padding-top:12px;">
              <a-avatar :src="robbot" v-if="item.role == 'assistant'" />
              <a-avatar :src="user" v-else />
            </div>
            <div style="flex: 0 0 8px"></div>
            <div style="flex: 1 1 auto">
              <div style="border-radius: 8px; background: #eee; padding: 8px">
                <div
                  v-if="
                    loading &&
                    item.role == 'assistant' &&
                    idx == chatListRevers.length - 1
                  "
                >
                  思考中 <a-spin :spinning="loading" size="small"></a-spin>
                </div>
                <div v-else>
                  <div v-if="item.thinking" style="border: 1px dotted #eee; padding: 8px">
                    {{ item.thinking }}
                  </div>
                  <Bytemd
                    v-if="item.role == 'assistant' && idx != 0"
                    v-model:value="item.content"
                    :canEdit="false"
                  ></Bytemd>
                  <div v-else>{{ item.content }}</div>
                </div>
              </div>
            </div>
          </div>

          <!-- {{loading}}
          {{chatList}} -->
        </div>

        <div style="flex: 0 auto; padding: 8px; display: flex">
          <div style="flex: 0 0 100px"></div>
          <div style="flex: 1 1 auto">
            <a-textarea
              placeholder="Input your question here ."
              :rows="2"
              v-model:value="question"
              @pressEnter="submitQuestion"
              :disabled="isStreamLoad || loading"
            />
            <div style="display: flex; margin-top: 8px; margin-bottom: 8px">
              <div style="flex: 1 1 auto">
                <a-space>
                  <a-tooltip>
                    <template #title>Upload Files</template>
                    <a-button style="height: 30px" size="small">
                      <FileImageOutlined />
                    </a-button>
                  </a-tooltip>

                  <a-tooltip>
                    <template #title>Setting</template>
                    <a-button style="height: 30px" size="small">
                      <SettingOutlined />
                    </a-button>
                  </a-tooltip>

                  <a-tooltip>
                    <template #title>Knowledge Library</template>
                    <a-button style="height: 30px" size="small">
                      <BookOutlined />
                    </a-button>
                  </a-tooltip>
                </a-space>
              </div>
              <div style="flex: 0 0 500px; text-align: right">
                <a-space>
                  
                  <a-dropdown >
                    <a class="ant-dropdown-link" @click.prevent>
                      {{ model }}
                      <DownOutlined />
                    </a>

                    <template #overlay>
                      <a-menu>
                        <a-menu-item
                          v-for="item in modelList"
                          :key="item.name"
                          @click="changeModel(item.name)"
                        >
                          <!-- <UserOutlined /> -->
                          <a-avatar :src="robbot" />
                          {{ item.name }}
                        </a-menu-item>

                        <!-- <a-menu-item key="1" @click="changeModel('qwen3:8b')">
                          <UserOutlined />
                          Qwen:7b
                        </a-menu-item>
                        <a-menu-item key="2" @click="changeModel('deepseek-r1:8b')">
                          <UserOutlined />
                          Deepseek:8b
                        </a-menu-item>
                        <a-menu-item key="3" @click="changeModel('deepseek-r1:7b')">
                          <UserOutlined />
                          Deepseek:1.5b
                        </a-menu-item> -->
                      </a-menu>
                    </template>
                   
                  </a-dropdown>

                  <a-radio-group v-model:value="apiType" size="small">
                    <a-radio-button value="chat">chat</a-radio-button>
                    <a-radio-button value="generate">generate</a-radio-button>
                    <a-radio-button value="embed">embed</a-radio-button>
                  </a-radio-group>


                  <a-tooltip>
                    <template #title>Submit your question</template>
                    <a-button
                      type="primary"
                      style="height: 30px"
                      size="small"
                      @click="submitQuestion"
                      :disabled="isStreamLoad || loading"
                    >
                      <ArrowUpOutlined />
                    </a-button>
                  </a-tooltip>

                  <a-tooltip>
                    <template #title>Stop Answer</template>
                    <a-button
                      type="primary"
                      style="height: 30px"
                      size="small"
                      @click="stopLoad"
                      :disabled="!isStreamLoad"
                    >
                      <PauseCircleOutlined />
                    </a-button>
                  </a-tooltip>

                </a-space>
              </div>
            </div>
          </div>
          <div style="flex: 0 0 100px"></div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import {
  UserOutlined,
  BookOutlined,
  CommentOutlined,
  DownOutlined,
  SettingOutlined,
  ArrowUpOutlined,
  ArrowLeftOutlined,
  MenuFoldOutlined,
  FileImageOutlined,
  SettingFilled,
  PauseCircleOutlined,
  LoginOutlined,
  LogoutOutlined,
} from "@ant-design/icons-vue";
import Bytemd from "@/components/mdeditor/bytemd/Bytemd.vue";
import robbot from "@/assets/images/robbot.png";
import user from "@/assets/images/user.png";

import { ref, onMounted,nextTick , computed } from "vue";

const chatList = ref([
  // {
  //   id: "1",
  //   text: "地球为什么是圆的",
  //   role: "user",
  // },
  // {
  //   id: "2",
  //   text: "因为它叫地`球`，因为它叫地`球`，因为它叫地`球`，因为它叫地`球`，因为它叫地`球`，因为它叫地`球`，因为它叫地`球`，因为它叫地`球`，，因为它叫地`球`，因为它叫地`球`，",
  //   role: "assistant",
  // }
]);

const chatListRevers = computed(() => {
  return [...chatList.value].reverse(); // 使用扩展运算符创建原数组的副本，避免直接修改原数组
});
onMounted(() => {
  chatList.value.unshift({
    id: Date.now(),
    role: "assistant",
    content: "你好,我是您的AI助手,请开始提问吧",
  });

  fetchModelList();
});

const dealThink = (message) => {
  // const r = message.content.split("</think>");
  // return {
  //   think: r[0],
  //   answer: r[1],
  // };

  // if( message.content.indexOf("</think>") == -1){
  //   return {
  //     think:message.content,
  //     answer:""
  //   }
  // }else{
  //   return {
  //     think:message.content.substring(0,message.content.indexOf("</think>")),
  //     answer:message.content.substring(message.content.indexOf("</think>"),message.content.length),
  //   }
  // }
  return message;
};

// 聊天框容器
const chatContainer = ref(null);
// 是否滚动到聊天框底部
const isScroll = ref(true)
function scrollToBottom() {
  if (chatContainer.value ) {
    chatContainer.value.scrollTop = chatContainer.value.scrollHeight;
  }
}


function handleScroll(){
  // console.log(chatContainer.value.scrollTop,chatContainer.value.scrollHeight,chatContainer.value.offsetHeight)
  if( (chatContainer.value.scrollTop + chatContainer.value.offsetHeight)== chatContainer.value.scrollHeight){
    isScroll.value = true;
  }else{
    isScroll.value = false;
  }

}

const question = ref("");

// api类型 ， 是chat还是generate
const apiType = ref("chat");
// 加载状态
const loading = ref(false);
// 是否流在加载中
const isStreamLoad = ref(false);
// AbortController对象用于取消SSE
const fetchAbort = ref(null);

// 选择的模型
const model = ref("qwen3:8b");

// 模型列表
const modelList = ref([]);

// 更换模型
const changeModel = (modelName) => {
  model.value = modelName;
};

// 提交问题
const submitQuestion = async () => {
  // alert(question.value)
  await handleData();
};

// const summaryChatHistory = () => {
//   let summary = `本次提问的内容是:${question.value},历史聊天记录的JSON格式是：${JSON.stringify(chatList.value)}`;
//   return summary;
// }

/**
 * 处理数据函数，用于向服务器发送消息并处理返回的流式数据
 * @param {string} inputValue - 用户输入的消息内容
 */
const handleData = async () => {
  chatList.value.unshift({
    id: Date.now(),
    role: "user",
    content: question.value,
  });

  chatList.value.unshift({
    id: Date.now(),
    role: "assistant",
    content: "",
    thinking:""
  });

  nextTick( ()=> {
    // 滚动到最底部
    isScroll.value = true;
  })
  
  // 设置加载状态为true
  loading.value = true;
  // 设置流式加载状态为true
  isStreamLoad.value = true;
  // 获取聊天列表的最后一个项
  const lastItem = chatList.value[0];
  // 构造消息对象，包含用户角色和内容
  const messages = [
    {
      role: "user",
      content: question.value,
    },
  ];
  
  
  // 调用服务器发送事件流（SSE）接口获取数据
  fetchSSE(messages, {
    // 接收到消息时的处理函数
    onMessage(message) {
      console.log("message", message);
      // 设置加载状态为false
      loading.value = false;
      // 将接收到的消息内容追加到聊天列表的最后一个项中
      lastItem.content += message.content ? message.content : "";
      lastItem.thinking += message.thinking ? message.thinking : ""
      // 聊天框滚动到最底部
      if(isScroll.value){
        scrollToBottom()
      }
    },
    // 发生错误时的处理函数
    onError(error) {
      // 设置消息角色为错误
      lastItem.role = "error";
      // 设置消息内容为错误信息
      lastItem.content = error;
      // 设置流式加载状态为false
      isStreamLoad.value = false;
      // 设置加载状态为false
      loading.value = false;
    },
    // 数据接收完成时的处理函数
    onComplete() {
      // 设置流式加载状态为false
      isStreamLoad.value = false;
      // 设置加载状态为false
      loading.value = false;

      // 清空问题内容
  question.value = "";
    },
  });

};

/**
 * 通过Server-Sent Events (SSE) 流式获取数据
 *
 * @param {Array} messages 消息列表，包含聊天历史或查询信息
 * @param {Object} options 配置对象，包含事件处理函数
 * @param {Function} options.onMessage 接收到消息时的回调函数
 * @param {Function} options.onError 发生错误时的回调函数
 * @param {Function} options.onComplete 流式传输完成时的回调函数
 */
// 调用服务器发送事件流（SSE）接口获取数据
const fetchSSE = async (messages, options) => {
  // 解构配置对象中的事件处理函数
  const { onMessage, onError, onComplete } = options;

  // 创建AbortController以支持取消请求
  const controller = new AbortController();
  // 获取AbortSignal用于fetch请求
  const { signal } = controller;
  // 将控制器赋值给全局或父作用域变量，以便于外部取消请求
  fetchAbort.value = controller;
  // 设置使用的模型名称(可在cmd中调用ollama list命令查看)
  const modelName = model.value;

  // 发起fetch请求，获取SSE响应   chat
  // const resPromise = fetch("http://localhost:11434/api/chat", {
  //   method: "POST",
  //   headers: {
  //     "Content-Type": "application/json",
  //   },
  //   body: JSON.stringify({
  //     messages, // 消息列表
  //     model: modelName, // 模型
  //     stream: true, // 流式
  //   }),
  //   signal,
  // });

  // 发起fetch请求，获取SSE响应   generate
  // const resPromise = fetch("http://localhost:11434/api/generate", {
  //   method: "POST",
  //   headers: {
  //     "Content-Type": "application/json",
  //   },
  //   body: JSON.stringify({
  //       "model": modelName,
  //       "prompt": question.value,
  //       "stream": true,
  //       // "options": { // 可选参数
  //       //     "temperature": 0.7, // 温度参数
  //       //     "max_tokens": 200 // 最大 token 数
  //       // }
  //   }),
  //   signal,
  // });

  const ollamaUrl =
    apiType.value == "chat"
      ? "http://localhost:11434/api/chat"
      : apiType.value == "generate" ? "http://localhost:11434/api/generate" : "http://localhost:11434/api/embed";

  const createRpcBody = function () {
    if (apiType.value == "chat") {
      return {
        messages, // 消息列表
        model: modelName, // 模型
        stream: true, // 流式
      };
    } else if (apiType.value == "generate") {
      return {
        model: modelName,
        prompt: question.value,
        stream: true,
      };
    } else if (apiType.value == "embed"){
      return {
        model: modelName,
        input: [question.value]
      };
    }
  };

  // 发起fetch请求，获取SSE响应   generate
  const resPromise = fetch(ollamaUrl, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(createRpcBody()),
    signal,
  });


  if(apiType.value == "embed"){
    resPromise.then(response => {
      response.json().then(data => {
        console.log("data", data);
        onMessage({content: data.embeddings[0]});
        onComplete();
      })
    })

   
  }

  // 处理响应
  resPromise
    .then(async (response) => {
      // console.log("response", response);

      // 检查HTTP状态码，如果非200，则调用错误处理函数并抛出错误
      if (!response.ok) {
        const { status } = response;
        onError(`HTTP error! status: ${status}`);
        throw new Error(`HTTP error! status: ${status}`);
      }

      // 获取响应体的Reader对象，用于逐块读取数据
      const reader = response.body.getReader();
      // 创建TextDecoder对象，用于解码数据块
      const decoder = new TextDecoder("utf-8");
      // 初始化缓冲区
      let buffer = "";
      // 循环读取数据块
      while (true) {
        // 读取下一数据块
        const { done, value } = await reader.read();
        // 如果done为true，则表示数据读取完毕
        if (done) {
          console.log("Stream complete");
          // 调用完成处理函数
          onComplete();
          return Promise.resolve();
        }

        // 将数据块解码为字符串并追加到缓冲区
        buffer += decoder.decode(value, { stream: true });
        // 按行分割缓冲区内容
        const lines = buffer.split("\n");
        // 更新缓冲区内容，保留未完整的一行
        buffer = lines.pop();
        // 遍历每一行数据
        for (const line of lines) {
          // 忽略空行
          if (line.trim() === "") continue;
          try {
            // 解析JSON数据
            const chunk = JSON.parse(line);
            // 如果数据块表示传输完成，则跳出循环
            if (chunk.done) {
              break;
            }

            console.log("--chunk", chunk);
            // 如果数据块包含消息内容，则调用消息处理函数 调用chat接口的返回内容
            if (chunk.message && (chunk.message?.content || chunk.message?.thinking)) {
              const { content,thinking } = chunk.message;
              // 调用消息处理函数
              onMessage({content,thinking});
            } else if (chunk.response || chunk.thinking) {
              // 调用generate接口的返回内容

              const { response,thinking } = chunk;
              // 调用消息处理函数
              onMessage({content:response,thinking});
            }
          } catch (error) {}
        }
      }
    })
    .catch((error) => {
      // 错误处理
      console.log("error", error);
    });
};

/**
 * 停止加载操作
 *
 * 此函数用于中断正在进行的加载过程它通过调用fetchAbort.value.abort()来终止网络请求，
 * 并将isStreamLoad.value和loading.value设置为false，以更新加载状态
 */
const stopLoad = function () {
  console.log("onStop");
  fetchAbort.value.abort();
  isStreamLoad.value = false;
  loading.value = false;
};

const fetchModelList = () => {
  fetch("http://localhost:11434/api/tags", {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  })
    .then((res) => res.json())
    .then((res) => {
      console.log(res);

      modelList.value = res.models;
    });
};
</script>

<style>
</style>